/*
 * Copyright (C) 2010 M. Hautle.
 * 
 * This file is part of SimpleEL.
 * 
 * SimpleEL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * SimpleEL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with SimpleEL. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.msoftch.internal;

/**
 * Classloader to define classes on the fly.<br>
 * The classes hold by this loader may be garbage collected, so there is no guarantee that they were available during the whole JVM lifetime!<br>
 * Consider to use the following 'pattern':<br>
 * 
 * <pre>
 * <code>
 * synchronized Class<?> getFoo(Class c){
 *    final String internal = c.getName() + "_foo";
 *    final ASMClassLoader loader = ASMClassLoader.getLoader(c.getClassLoader());
 *    Class cl = loader.getLoadedClass(internal);
 *    if (cl == null)
 *       cl = loader.defineClass(internal, buildByteCode(internal, c));
 *    return cl;
 * }
 * </code>
 * </pre>
 * 
 * Set the systemproperty <code>asmLoaderTTL</code> to the maximum of classes to define before a classloader get's explicit dereferenced.
 * 
 * @author M. Hautle
 */
public final class ASMClassLoader extends ClassLoader {
    /** System property name for {@link #MAX_TTL}. */
    private static final String MAX_TTL_PROPERTY = "asmLoaderTTL";

    /** Map mapping {@link ASMClassLoader}s to their parent classloaders. */
    private static ICache<ClassLoader, ASMClassLoader> LOADERS = CacheFactory.createCache(ClassLoader.class, ASMClassLoader.class);

    /** Maximum value for {@link #ttl}. */
    private static final long MAX_TTL;

    /**
     * Time to life for this classloader, after it has loaded the specified number of classes it will be replaced in {@link #LOADERS} with a new loader. This to
     * make working class unloading a bit better...
     */
    private long ttl = MAX_TTL;

    static {
        final String max = System.getProperty(MAX_TTL_PROPERTY);
        MAX_TTL = max != null ? Long.parseLong(max) : Long.MAX_VALUE;
    }

    /**
     * Hidden constructor.
     * 
     * @param parent The parent classloader
     */
    private ASMClassLoader(ClassLoader parent) {
        super(parent);
    }

    /**
     * Returns a suitable class loader instance.<br>
     * This method creates a new instance if necessary.
     * 
     * @param classLoader The 'parent' classloader
     * @return The corresponding instance
     */
    public static synchronized ASMClassLoader getLoader(ClassLoader classLoader) {
        // replace bootstrap classloader - otherwise we won't find our own classes :(
        if (classLoader == null)
            classLoader = getSystemClassLoader();
        ASMClassLoader cl = LOADERS.get(classLoader);
        if (cl != null && cl.ttl > 0)
            return cl;
        LOADERS.put(classLoader, cl = new ASMClassLoader(classLoader));
        return cl;
    }

    /**
     * Returns the class with the given name if it exist.<br>
     * Call this method only in a synchronized method!
     * 
     * @param name The name of the class
     * @return The class or null
     */
    public final Class<?> getLoadedClass(String name) {
        return findLoadedClass(name);
    }

    /**
     * Defines a class.<br>
     * Call this method only in a synchronized method!
     * 
     * @param name The name of the class
     * @param code The byte code of the class
     * @return The class
     */
    public final Class<?> defineClass(String name, byte[] code) {
        final Class<?> cl = defineClass(name, code, 0, code.length);
        // we created a class, count TTL down
        ttl--;
        return cl;
    }

    /**
     * Drops all classloaders.<br>
     * Consider to use this method only for testing purposes.
     */
    public static void clearLoaders() {
        LOADERS.clear();
    }
}
